/*
 * md5mesh.c -- md5mesh model loader + animation
 *
 * Doom3's md5mesh viewer with animation.  Mesh portion.
 * Dependences: md5model.h, md5anim.c.
 *
 * Copyright (c) 2005-2007 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "md5model.h"

static int max_verts = 0;
static int max_tris = 0;

/**
 * Basic quaternion operations.
 */
/////////////////////////////////////////////////////////////////////////////////////////////////
void
Quat_computeW(quat4_t q) {
    float t = 1.0f - (q[X] * q[X]) - (q[Y] * q[Y]) - (q[Z] * q[Z]);

    if (t < 0.0f)
        q[W] = 0.0f;
    else
        q[W] = -sqrt(t);
}

void
Quat_normalize(quat4_t q) {
    /* compute magnitude of the quaternion */
    float mag = sqrt((q[X] * q[X]) + (q[Y] * q[Y])
            + (q[Z] * q[Z]) + (q[W] * q[W]));

    /* check for bogus length, to protect against divide by zero */
    if (mag > 0.0f) {
        /* normalize it */
        float oneOverMag = 1.0f / mag;

        q[X] *= oneOverMag;
        q[Y] *= oneOverMag;
        q[Z] *= oneOverMag;
        q[W] *= oneOverMag;
    }
}

void
Quat_multQuat(const quat4_t qa, const quat4_t qb, quat4_t out) {
    out[W] = (qa[W] * qb[W]) - (qa[X] * qb[X]) - (qa[Y] * qb[Y]) - (qa[Z] * qb[Z]);
    out[X] = (qa[X] * qb[W]) + (qa[W] * qb[X]) + (qa[Y] * qb[Z]) - (qa[Z] * qb[Y]);
    out[Y] = (qa[Y] * qb[W]) + (qa[W] * qb[Y]) + (qa[Z] * qb[X]) - (qa[X] * qb[Z]);
    out[Z] = (qa[Z] * qb[W]) + (qa[W] * qb[Z]) + (qa[X] * qb[Y]) - (qa[Y] * qb[X]);
}

void
Quat_multVec(const quat4_t q, const float3 v, quat4_t out) {
    out[W] = -(q[X] * v[X]) - (q[Y] * v[Y]) - (q[Z] * v[Z]);
    out[X] = (q[W] * v[X]) + (q[Y] * v[Z]) - (q[Z] * v[Y]);
    out[Y] = (q[W] * v[Y]) + (q[Z] * v[X]) - (q[X] * v[Z]);
    out[Z] = (q[W] * v[Z]) + (q[X] * v[Y]) - (q[Y] * v[X]);
}

void
Quat_rotatePoint(const quat4_t q, const float3 in, float3 out) {
    quat4_t tmp, inv, final;

    inv[X] = -q[X];
    inv[Y] = -q[Y];
    inv[Z] = -q[Z];
    inv[W] = q[W];

    Quat_normalize(inv);

    Quat_multVec(q, in, tmp);
    Quat_multQuat(tmp, inv, final);

    out[X] = final[X];
    out[Y] = final[Y];
    out[Z] = final[Z];
}
///////////////////////////////////////////////////////////////////////////////////////////////

/**
 * Load an MD5 model from file.
 */
int
gsubReadMD5Model(const char *filename, struct _md5_model *mdl, struct _md5_anim *anim) {
    FILE *fp;
    char buff[512];
    int version;
    int curr_mesh = 0;
    int i;

    fp = fopen(filename, "rb");
    if (!fp) {
        MGPI_PostMessagef("%s: couldn't open \"%s\"", __FUN__, filename);
        return 0;
    }

    while (!feof(fp)) {
        /* Read whole line */
        fgets(buff, sizeof (buff), fp);

        if (sscanf(buff, " MD5Version %d", &version) == 1) {
            if (version != 10) {
                /* Bad version */
                MGPI_PostMessagef("%s: bad model version: version = %d", __FUN__, version);
                fclose(fp);
                return 0;
            }
        } else if (sscanf(buff, " numJoints %d", &mdl->num_joints) == 1) {
            if (mdl->num_joints > 0) {
                /* Allocate memory for base skeleton joints */
                mdl->baseSkel = (struct _md5_joint *)
                        calloc(mdl->num_joints, sizeof (struct _md5_joint));
            }
        } else if (sscanf(buff, " numMeshes %d", &mdl->num_meshes) == 1) {
            if (mdl->num_meshes > 0) {
                /* Allocate memory for meshes */
                mdl->meshes = (struct _md5_mesh *)
                        calloc(mdl->num_meshes, sizeof (struct _md5_mesh));
            }
        } else if (strncmp(buff, "joints {", 8) == 0) {
            /* Read each joint */
            for (i = 0; i < mdl->num_joints; ++i) {
                struct _md5_joint *joint = &mdl->baseSkel[i];

                /* Read whole line */
                fgets(buff, sizeof (buff), fp);

                if (sscanf(buff, "%s %d ( %f %f %f ) ( %f %f %f )",
                        joint->name, &joint->parent, &joint->pos[0],
                        &joint->pos[1], &joint->pos[2], &joint->orient[0],
                        &joint->orient[1], &joint->orient[2]) == 8) {
                    /* Compute the w component */
                    Quat_computeW(joint->orient);
                }
            }
        } else if (strncmp(buff, "mesh {", 6) == 0) {
            struct _md5_mesh *mesh = &mdl->meshes[curr_mesh];
            int vert_index = 0;
            int tri_index = 0;
            int weight_index = 0;
            float fdata[4];
            int idata[3];

            while ((buff[0] != '}') && !feof(fp)) {
                /* Read whole line */
                fgets(buff, sizeof (buff), fp);

                if (strstr(buff, "shader ")) {
                    int quote = 0, j = 0;

                    /* Copy the shader name whithout the quote marks */
                    for (i = 0; i < sizeof (buff) && (quote < 2); ++i) {
                        if (buff[i] == '\"')
                            quote++;

                        if ((quote == 1) && (buff[i] != '\"')) {
                            mesh->shader[j] = buff[i];
                            j++;
                        }
                    }
                } else
				if (sscanf(buff, " numverts %d", &mesh->num_verts) == 1) {
                    if (mesh->num_verts > 0) {
                        /* Allocate memory for vertices */
                        mesh->vertices = (struct _md5_vertex *)
                                malloc(sizeof (struct _md5_vertex) * mesh->num_verts);
                    }

                    if (mesh->num_verts > max_verts)
                        max_verts = mesh->num_verts;
                } else if (sscanf(buff, " numtris %d", &mesh->num_tris) == 1) {
                    if (mesh->num_tris > 0) {
                        /* Allocate memory for triangles */
                        mesh->triangles = (struct _md5_triangle *)
                                malloc(sizeof (struct _md5_triangle) * mesh->num_tris);
                    }

                    if (mesh->num_tris > max_tris)
                        max_tris = mesh->num_tris;
                } else if (sscanf(buff, " numweights %d", &mesh->num_weights) == 1) {
                    if (mesh->num_weights > 0) {
                        /* Allocate memory for vertex weights */
                        mesh->weights = (struct _md5_weight *)
                                malloc(sizeof (struct _md5_weight) * mesh->num_weights);
                    }
                } else if (sscanf(buff, " vert %d ( %f %f ) %d %d", &vert_index,
                        &fdata[0], &fdata[1], &idata[0], &idata[1]) == 5) {
                    /* Copy vertex data */
                    mesh->vertices[vert_index].st[0] = fdata[0];
                    mesh->vertices[vert_index].st[1] = fdata[1];
                    mesh->vertices[vert_index].start = idata[0];
                    mesh->vertices[vert_index].count = idata[1];
                } else if (sscanf(buff, " tri %d %d %d %d", &tri_index,
                        &idata[0], &idata[1], &idata[2]) == 4) {
                    /* Copy triangle data */
                    mesh->triangles[tri_index].index[0] = idata[0];
                    mesh->triangles[tri_index].index[1] = idata[1];
                    mesh->triangles[tri_index].index[2] = idata[2];

                } else if (sscanf(buff, " weight %d %d %f ( %f %f %f )",
                        &weight_index, &idata[0], &fdata[3],
                        &fdata[0], &fdata[1], &fdata[2]) == 6) {
                    /* Copy vertex data */
                    mesh->weights[weight_index].joint = idata[0];
                    mesh->weights[weight_index].bias = fdata[3];
                    mesh->weights[weight_index].pos[0] = fdata[0];
                    mesh->weights[weight_index].pos[1] = fdata[1];
                    mesh->weights[weight_index].pos[2] = fdata[2];
                }
            }

            curr_mesh++;
        }
    }

    fclose(fp);

    mdl->vertex_array = (float3 *) malloc(sizeof (float3) * max_verts);
    mdl->index_array = (GLuint *) malloc(sizeof (GLuint) * max_tris * 3);
	mdl->texcoord_array = (float4 *) malloc(sizeof (float4) * max_verts);
	mdl->normal_array = (float3 *) malloc(sizeof (float3) * max_verts);
	mdl->binormal_array = (float3 *) malloc(sizeof (float3) * max_verts);
	mdl->tangent_array = (float3 *) malloc(sizeof (float3) * max_verts);

/*
for every triangle
     computeNormalsCrossProductForTriangleVerts();
     computeTangentSpaceVectors();

for every vertex
     for every vertexWeight
          quat = inverseQuaternion(vertexWeight.quaternion);
          weightNormal = rotateNormalOverQuaternion(quat);
          weightTangentSpaceVectors = rotateTangentVectorsOverQuaternion(quat);
*/

    return 1;
}

/**
 * Free resources allocated for the model.
 */
void
gsubFreeMD5Model(struct _md5_model *mdl) {
    int i;

    if (mdl->baseSkel) {
        free(mdl->baseSkel);
        mdl->baseSkel = NULL;
    }

    if (mdl->meshes) {
        /* Free mesh data */
        for (i = 0; i < mdl->num_meshes; ++i) {
            if (mdl->meshes[i].vertices) {
                free(mdl->meshes[i].vertices);
                mdl->meshes[i].vertices = NULL;
            }

            if (mdl->meshes[i].triangles) {
                free(mdl->meshes[i].triangles);
                mdl->meshes[i].triangles = NULL;
            }

            if (mdl->meshes[i].weights) {
                free(mdl->meshes[i].weights);
                mdl->meshes[i].weights = NULL;
            }
        }

        free(mdl->meshes);
        mdl->meshes = NULL;
    }

	if (mdl->index_array) {
		free(mdl->index_array);
		mdl->index_array = NULL;
	}

	if (mdl->normal_array) {
		free(mdl->normal_array);
		mdl->normal_array = NULL;
	}

	if (mdl->binormal_array) {
		free(mdl->binormal_array);
		mdl->binormal_array = NULL;
	}

	if (mdl->tangent_array) {
		free(mdl->tangent_array);
		mdl->tangent_array = NULL;
	}
}

/**
 * Prepare a mesh for drawing.  Compute mesh's final vertex positions
 * given a skeleton.  Put the vertices in vertex arrays.
 */
void
gsubPrepareMesh(const struct _md5_mesh *mesh, const struct _md5_joint *skeleton, struct _md5_model *mdl) {
    int i, j, k;

    /* Setup vertex indices */
    for (k = 0, i = 0; i < mesh->num_tris; ++i) {
        for (j = 0; j < 3; ++j, ++k) mdl->index_array[k] = mesh->triangles[i].index[j];
    }

    /* Setup vertices */
    for (i = 0; i < mesh->num_verts; ++i) {
        float3 finalVertex = {0.0f, 0.0f, 0.0f};
		float3 normal = {0.0f, 0.0f, 0.0f};
		float4 texcoords = MGPI_DEFAULT_VECTOR;

        /* Calculate final vertex to draw with weights */
        for (j = 0; j < mesh->vertices[i].count; ++j) {
            const struct _md5_weight *weight = &mesh->weights[mesh->vertices[i].start + j];
            const struct _md5_joint *joint = &skeleton[weight->joint];

            /* Calculate transformed vertex for this weight */
            float3 wv;
            Quat_rotatePoint(joint->orient, weight->pos, wv);

            /* The sum of all weight->bias should be 1.0 */
            finalVertex[X] += (joint->pos[0] + wv[0]) * weight->bias;
            finalVertex[Y] += (joint->pos[1] + wv[1]) * weight->bias;
            finalVertex[Z] += (joint->pos[2] + wv[2]) * weight->bias;

			//??????????????????????????????????????????????
			//mesh->vertices[i].pos[X] = finalVertex[X];
			//mesh->vertices[i].pos[Y] = finalVertex[Y];
			//mesh->vertices[i].pos[Z] = finalVertex[Z];

			/* Texture coordinates (?)*/
			texcoords[S] += mesh->vertices[i].st[S] * weight->bias;//?????
			texcoords[T] += mesh->vertices[i].st[T] * weight->bias;//?????

			/* Normal vector */
			normal[X] += (mesh->vertices[i].normal[X] * joint->orient[X]) * weight->bias;
			normal[Y] += (mesh->vertices[i].normal[Y] * joint->orient[Y]) * weight->bias;
			normal[Z] += (mesh->vertices[i].normal[Z] * joint->orient[Z]) * weight->bias;

			MGPI_Normalize(normal);

			//...
        }

        mdl->vertex_array[i][X] = finalVertex[X];
        mdl->vertex_array[i][Y] = finalVertex[Y];
        mdl->vertex_array[i][Z] = finalVertex[Z];

		mdl->texcoord_array[i][S] = texcoords[S];
		mdl->texcoord_array[i][T] = texcoords[T];

		mdl->normal_array[i][X] = normal[X];
		mdl->normal_array[i][Y] = normal[Y];
		mdl->normal_array[i][Z] = normal[Z];

		//...
    }
}

/*
bool MD5Model::PrepareNormals( Mesh& mesh )
{
    mesh.m_NormalBuffer.clear();
 
    // Loop through all triangles and calculate the normal of each triangle
    for ( unsigned int i = 0; i < mesh.m_Tris.size(); ++i )
    {
        glm::vec3 v0 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices[0] ].m_Pos;
        glm::vec3 v1 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices[1] ].m_Pos;
        glm::vec3 v2 = mesh.m_Verts[ mesh.m_Tris[i].m_Indices[2] ].m_Pos;
 
        glm::vec3 normal = glm::cross( v2 - v0, v1 - v0 );
 
        mesh.m_Verts[ mesh.m_Tris[i].m_Indices[0] ].m_Normal += normal;
        mesh.m_Verts[ mesh.m_Tris[i].m_Indices[1] ].m_Normal += normal;
        mesh.m_Verts[ mesh.m_Tris[i].m_Indices[2] ].m_Normal += normal;
    }
 
    // Now normalize all the normals
    for ( unsigned int i = 0; i < mesh.m_Verts.size(); ++i )
    {
        Vertex& vert = mesh.m_Verts[i];
 
        glm::vec3 normal = glm::normalize( vert.m_Normal );
        mesh.m_NormalBuffer.push_back( normal );
 
        // Reset the normal to calculate the bind-pose normal in joint space
        vert.m_Normal = glm::vec3(0);
 
        // Put the bind-pose normal into joint-local space
        // so the animated normal can be computed faster later
        for ( int j = 0; j < vert.m_WeightCount; ++j )
        {
            const Weight& weight = mesh.m_Weights[vert.m_StartWeight + j];
            const Joint& joint = m_Joints[weight.m_JointID];
            vert.m_Normal += ( normal * joint.m_Orient ) * weight.m_Bias;
        }
    }
 
    return true;
}
*/

void
gsubRenderMD5Model(struct _md5_model *mdl, struct _md5_anim *anim, int start, int stop, int num_passes) {
    int i;
    //static double curent_time = 0;
    //static double last_time = 0;
    //static int k = 0;
	struct _anim_info *animInfo = &anim->info;

  //  if (num_passes <= 1) {/* Note: probably multipass will not be supported... */
  //      //last_time = curent_time;
		//animInfo->curr_time = (double) SDL_GetTicks() / 1000.0;
  //  } else if (!k) {
  //      //last_time = curent_time;
  //      animInfo->curr_time = (double) SDL_GetTicks() / 1000.0;
  //      ++k;
  //  } else if (k && k < num_passes) ++k;
  //  else if (k == num_passes) k = 0;

	animInfo->curr_time = (double) SDL_GetTicks() / 1000.0;

    gsubAnimateMD5(anim, animInfo, (animInfo->curr_time - animInfo->last_time), start, stop);

    gsubInterpolateSkeletons(anim->skelFrames[animInfo->curr_frame], anim->skelFrames[animInfo->next_frame],
            anim->num_joints, animInfo->last_time * anim->frameRate,
            mdl->skeleton_array);

    glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	//glEnableClientState(GL_NORMAL_ARRAY);
    {
        for (i = 0; i < mdl->num_meshes; ++i) {
            gsubPrepareMesh(&mdl->meshes[i], mdl->skeleton_array, mdl);

            glVertexPointer(3, GL_FLOAT, 0, mdl->vertex_array);
			glTexCoordPointer(4, GL_FLOAT, 0, mdl->texcoord_array);
			//glNormalPointer(3, 0, mdl->normal_array);

			// TODO: other vertex attributes!!!!

            glDrawElements(GL_TRIANGLES, mdl->meshes[i].num_tris * 3, GL_UNSIGNED_INT, mdl->index_array);
        }
    }
    glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	//glDisableClientState(GL_NORMAL_ARRAY);

    return;
}

/*
 * Draw the skeleton as lines and points (for joints).
 *
void
drawSkeleton (const struct md5_joint_t *skeleton, int num_joints)
{
  int i;

  glPointSize (5.0f);
  glColor3f (1.0f, 0.0f, 0.0f);
  glBegin (GL_POINTS);
    for (i = 0; i < num_joints; ++i)
      glVertex3fv (skeleton[i].pos);
  glEnd ();
  glPointSize (1.0f);

  glColor3f (0.0f, 1.0f, 0.0f);
  glBegin (GL_LINES);
    for (i = 0; i < num_joints; ++i)
      {
        if (skeleton[i].parent != -1)
          {
            glVertex3fv (skeleton[skeleton[i].parent].pos);
            glVertex3fv (skeleton[i].pos);
          }
      }
  glEnd();
}

void
init (const char *filename, const char *animfile)
{
  glClearColor (0.5f, 0.5f, 0.5f, 1.0f);
  glShadeModel (GL_SMOOTH);

  glEnable (GL_DEPTH_TEST);

  if (!ReadMD5Model (filename, &md5file))
    exit (EXIT_FAILURE);

  AllocVertexArrays ();

  if (animfile)
    {
      if (!ReadMD5Anim (animfile, &md5anim))
        {
          FreeAnim (&md5anim);
        }
      else
        {
          animInfo.curr_frame = 0;
          animInfo.next_frame = 1;

          animInfo.last_time = 0;
          animInfo.max_time = 1.0 / md5anim.frameRate;

          skeleton = (struct md5_joint_t *)
            malloc (sizeof (struct md5_joint_t) * md5anim.num_joints);

          animated = 1;
        }
    }

  if (!animated)
    printf ("init: no animation loaded.\n");
}

void
cleanup ()
{
  FreeModel (&md5file);
  FreeAnim (&md5anim);

  if (animated && skeleton)
    {
      free (skeleton);
      skeleton = NULL;
    }

  FreeVertexArrays ();
}

void
reshape (int w, int h)
{
  if (h == 0)
    h = 1;

  glViewport (0, 0, (GLsizei)w, (GLsizei)h);

  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  gluPerspective (45.0, w/(GLdouble)h, 0.1, 1000.0);

  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

void
display ()
{
  int i;
  static float angle = 0;
  static double curent_time = 0;
  static double last_time = 0;

  last_time = curent_time;
  curent_time = (double)glutGet (GLUT_ELAPSED_TIME) / 1000.0;

  glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glLoadIdentity ();

  glPolygonMode (GL_FRONT_AND_BACK, GL_LINE);

  glTranslatef (0.0f, -35.0f, -150.0f);
  glRotatef (-90.0f, 1.0, 0.0, 0.0);
  glRotatef (angle, 0.0, 0.0, 1.0);

  angle += 25 * (curent_time - last_time);

  if (angle > 360.0f)
    angle -= 360.0f;

  if (animated)
    {
      Animate (&md5anim, &animInfo, curent_time - last_time);

      InterpolateSkeletons (md5anim.skelFrames[animInfo.curr_frame],
                            md5anim.skelFrames[animInfo.next_frame],
                            md5anim.num_joints,
                            animInfo.last_time * md5anim.frameRate,
                            skeleton);
    }
  else
    {
      skeleton = md5file.baseSkel;
    }

  DrawSkeleton (skeleton, md5file.num_joints);

  glColor3f (1.0f, 1.0f, 1.0f);

  glEnableClientState (GL_VERTEX_ARRAY);

  for (i = 0; i < md5file.num_meshes; ++i)
    {
      PrepareMesh (&md5file.meshes[i], skeleton);

      glVertexPointer (3, GL_FLOAT, 0, vertexArray);

      glDrawElements (GL_TRIANGLES, md5file.meshes[i].num_tris * 3,
                      GL_UNSIGNED_INT, vertexIndices);
    }

  glDisableClientState (GL_VERTEX_ARRAY);

  glutSwapBuffers ();
  glutPostRedisplay ();
}

void
keyboard (unsigned char key, int x, int y)
{
  if (key == 27)
    exit (0);
}

int
main (int argc, char *argv[])
{
  if (argc < 2)
    {
      fprintf (stderr, "usage: %s <filename.md5mesh> "
               "[<filename.md5anim>]\n", argv[0]);
      return 0;
    }

  glutInit (&argc, argv);
  glutInitDisplayMode (GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
  glutInitWindowSize (640, 480);
  glutCreateWindow ("MD5 Model");

  atexit (cleanup);
  init (argv[1], (argc > 2) ? argv[2] : NULL);

  glutReshapeFunc (reshape);
  glutDisplayFunc (display);
  glutKeyboardFunc (keyboard);

  glutMainLoop ();

  return 0;
}
 */
